package com.java1234.util;

import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.InitiateMultipartUploadRequest;
import com.aliyun.oss.model.InitiateMultipartUploadResult;
import com.aliyun.oss.model.PartETag;
import com.aliyun.oss.model.UploadPartRequest;
import com.aliyun.oss.model.CompleteMultipartUploadRequest;
import com.java1234.config.AliOssProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * @author 邓清文
 * @version 1.0
 * @description:
 * @since 2024/12/16 15:34
 */
@Slf4j
@Component
public class AliOssUtils {

    private final AliOssProperties aliOssProperties;

    @Autowired
    public AliOssUtils(AliOssProperties aliOssProperties) {
        this.aliOssProperties = aliOssProperties;
    }

    /**
     * 每个分片的大小，1 MB。
     */
    private static final int PART_SIZE = 1024 * 1024;

    /**
     * 保存上传状态的文件
     */
    private static final String UPLOAD_STATE_FILE = "upload_state.properties";

    /**
     * 上传文件（支持断点续传）。
     *
     * @param objectName OSS上的文件名
     * @param in         文件的 InputStream
     * @return 上传后的文件URL
     * @throws IOException 如果上传过程中发生IO错误
     * @throws OSSException 如果OSS操作发生错误
     * @throws ClientException 如果客户端操作发生错误
     */
    public String uploadFile(String objectName, InputStream in) throws IOException, OSSException, ClientException {
        // 创建OSS客户端实例
        OSS ossClient = new OSSClientBuilder().build(aliOssProperties.getEndpoint(), aliOssProperties.getAccessKeyId(), aliOssProperties.getAccessKeySecret());
        String url = "";

        try {
            // 1. 获取或初始化分片上传
            InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(aliOssProperties.getBucketName(), objectName);
            // 发起分片上传请求
            InitiateMultipartUploadResult initiateMultipartUploadResult = ossClient.initiateMultipartUpload(initiateMultipartUploadRequest);
            String uploadId = initiateMultipartUploadResult.getUploadId();

            // 2. 读取之前上传的状态 从本地文件加载已上传的分片信息
            List<PartETag> partEtags = loadUploadState(objectName);

            // 3. 分片上传
            // 创建缓冲区，大小为每个分片的大小
            byte[] buffer = new byte[PART_SIZE];
            // 计算已上传的分片数量
            int partCount = partEtags.size();
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                // 从输入流中读取数据
                partCount++;
                if (partEtags.size() >= partCount) {
                    // 如果该分片已经上传，则跳过
                    continue;
                }

                // 创建分片上传请求
                UploadPartRequest uploadPartRequest = new UploadPartRequest();
                uploadPartRequest.setBucketName(aliOssProperties.getBucketName());
                uploadPartRequest.setKey(objectName);
                uploadPartRequest.setUploadId(uploadId);
                uploadPartRequest.setPartNumber(partCount);
                uploadPartRequest.setInputStream(new ByteArrayInputStream(buffer, 0, bytesRead));
                uploadPartRequest.setPartSize(bytesRead);

                // 上传分片
                PartETag partEtag = ossClient.uploadPart(uploadPartRequest).getPartETag();
                partEtags.add(partEtag);

                // 保存上传状态
                saveUploadState(objectName, partEtags);
            }

            // 4. 完成分片上传，合并分片
            CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(aliOssProperties.getBucketName(), objectName, uploadId, partEtags);
            ossClient.completeMultipartUpload(completeMultipartUploadRequest);

            // 生成文件访问URL
            url = "https://" + aliOssProperties.getBucketName() + "." + aliOssProperties.getEndpoint().substring(aliOssProperties.getEndpoint().lastIndexOf("/") + 1) + "/" + objectName;

        } catch (OSSException | ClientException oe) {
            // 处理OSS异常
            log.error("Error uploading to OSS: " + oe.getMessage(), oe);
            throw oe;
        } catch (IOException ioe) {
            // 处理IO异常
            log.error("Error handling input stream: " + ioe.getMessage(), ioe);
            throw ioe;
        } finally {
            if (ossClient != null) {
                ossClient.shutdown(); // 关闭OSS客户端
            }
        }
        clearUploadState();
        return url;
    }

    /**
     * 从本地文件加载上传状态。
     *
     * @param objectName 文件名
     * @return 已上传分片的信息
     * @throws IOException 如果读取文件时发生错误
     */
    private List<PartETag> loadUploadState(String objectName) throws IOException {
        // 创建一个列表用于存储已上传的分片信息
        List<PartETag> partEtags = new ArrayList<>();
        File file = new File(UPLOAD_STATE_FILE);
        if (file.exists()) {
            Properties props = new Properties();
            try (InputStream inputStream = new FileInputStream(file)) {
                props.load(inputStream);
                String uploadedParts = props.getProperty(objectName);
                if (uploadedParts != null) {
                    String[] parts = uploadedParts.split(",");
                    for (String part : parts) {
                        String[] partInfo = part.split(":");
                        if (partInfo.length == 2) {
                            partEtags.add(new PartETag(Integer.parseInt(partInfo[0]), partInfo[1]));
                        } else {
                            log.warn("Invalid part info format: " + part);
                        }
                    }
                }
            }
        }
        // 返回已上传的分片信息列表
        return partEtags;
    }

    /**
     * 清空 upload_state.properties 文件的内容。
     *
     * @throws IOException 如果写入文件时发生错误
     */
    private void clearUploadState() throws IOException {
        File file = new File(UPLOAD_STATE_FILE);
        if (file.exists()) {
            try (FileWriter writer = new FileWriter(file)) {
                writer.write("");
            }
        }
    }

    /**
     * 保存上传状态到本地。
     *
     * @param objectName 文件名
     * @param partEtags  已上传的分片信息
     * @throws IOException 如果写入文件时发生错误
     */
    private void saveUploadState(String objectName, List<PartETag> partEtags) throws IOException {
        Properties props = new Properties();
        File file = new File(UPLOAD_STATE_FILE);
        if (file.exists()) {
            try (InputStream inputStream = new FileInputStream(file)) {
                props.load(inputStream);
            }
        }
        StringBuilder sb = new StringBuilder();
        for (PartETag partEtag : partEtags) {
            sb.append(partEtag.getPartNumber()).append(":").append(partEtag.getETag()).append(",");
        }
        props.setProperty(objectName, sb.toString());
        try (OutputStream outputStream = new FileOutputStream(file)) {
            props.store(outputStream, "Upload State");
        }
    }
}
